package engine.scene;

import static org.lwjgl.opengl.EXTFramebufferObject.*;
import static org.lwjgl.opengl.EXTFramebufferMultisample.*;
import static org.lwjgl.opengl.EXTFramebufferBlit.*;
import static org.lwjgl.opengl.GL11.*;

import java.nio.IntBuffer;

import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.GLContext;

import engine.util.fbo.FBO;

public class FBORenderer {

	private int texID;
	private int depthTexID;
	
	private int width;
	private int height;
	private int fbo;
	private int multisampleFBO;
	private int sampling = 8;
	
	private boolean multisampling = false;
	private boolean enableMultisampling;
	
	public FBORenderer(int width, int height, boolean enableMultisampling) {
		this.width = width;
		this.height = height;
		this.enableMultisampling = enableMultisampling;
		
		init();
	}
	
	private void init() {
		if(isEnableMultisampling()) {
			multisampling = isMultisampling();
		}
				
		fbo = glGenFramebuffersEXT();

		texID = glGenTextures();

		depthTexID = glGenTextures();

		glBindTexture(GL_TEXTURE_2D, depthTexID);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		IntBuffer depthBuffer = BufferUtils.createIntBuffer(width * height);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, depthBuffer);
		
		glBindTexture(GL_TEXTURE_2D, texID);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
		IntBuffer textureBuffer = BufferUtils.createIntBuffer(width * height * 4);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_FLOAT, textureBuffer);

		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, depthTexID, 0);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texID, 0);
		
		FBO.checkForErrors(fbo);
		
		if(multisampling) {
			sampling = Math.min(GL_MAX_SAMPLES_EXT, sampling);
			
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
			
			// multi sampled color buffer
			int colorBufferID = glGenRenderbuffersEXT();
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, colorBufferID);
			glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampling, GL_RGBA, width, height);

			FBO.checkForErrors(colorBufferID);
			
			// multi sampled depth buffer
			int depthBufferID = glGenRenderbuffersEXT();
			glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, depthBufferID);
			glRenderbufferStorageMultisampleEXT(GL_RENDERBUFFER_EXT, sampling, GL_DEPTH_COMPONENT, width, height);

			FBO.checkForErrors(depthBufferID);
			
			// unbind
			glBindRenderbufferEXT( GL_RENDERBUFFER_EXT, 0);

			// create fbo for multi sampled content and attach buffers
			multisampleFBO = glGenFramebuffersEXT();
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, multisampleFBO);
			glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_RENDERBUFFER_EXT, colorBufferID);
			glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, depthBufferID);
			
			FBO.checkForErrors(multisampleFBO);
		}

		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	}
	
	public void preRender() {
		if(multisampling) {
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, multisampleFBO);
		} else {
			glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo);
		}
		
		glClearColor(0, 0, 0, 0);
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		
		glPushMatrix();
		glLoadIdentity();
	}
	
	public void postRender() {
		glPopMatrix();
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
		
		if(multisampling) {
			glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, multisampleFBO);
			glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, fbo);
			glBlitFramebufferEXT(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT, GL_NEAREST);
			glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, 0);
			glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, 0);
		}
	}
	
	public int getDepthTexID() {
		return depthTexID;
	}
	
	public int getTexID() {
		return texID;
	}
	
	public int getWidth() {
		return width;
	}
	
	public int getHeight() {
		return height;
	}
	
	private boolean isMultisampling() {
		ContextCapabilities c = GLContext.getCapabilities();
		return c.GL_EXT_framebuffer_blit && c.GL_EXT_framebuffer_multisample;
	}

	public boolean isEnableMultisampling() {
		return enableMultisampling;
	}
}
